/*
 Copyright (c) 2024 HigginsSoft, Alexander Higgins - https://github.com/alexhiggins732/ 

 Copyright (c) 2018, Brock Allen & Dominick Baier. All rights reserved.

 Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 
 Source code and license this software can be found 

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.
*/

namespace IdentityServer8.AspNetIdentity;

/// <summary>
/// IResourceOwnerPasswordValidator that integrates with ASP.NET Identity.
/// </summary>
/// <typeparam name="TUser">The type of the user.</typeparam>
/// <seealso cref="IdentityServer8.Validation.IResourceOwnerPasswordValidator" />
public class ResourceOwnerPasswordValidator<TUser> : IResourceOwnerPasswordValidator
    where TUser : class
{
    private readonly SignInManager<TUser> _signInManager;
    private readonly UserManager<TUser> _userManager;
    private readonly ILogger<ResourceOwnerPasswordValidator<TUser>> _logger;

    /// <summary>
    /// Initializes a new instance of the <see cref="ResourceOwnerPasswordValidator{TUser}"/> class.
    /// </summary>
    /// <param name="userManager">The user manager.</param>
    /// <param name="signInManager">The sign in manager.</param>
    /// <param name="logger">The logger.</param>
    public ResourceOwnerPasswordValidator(
        UserManager<TUser> userManager,
        SignInManager<TUser> signInManager,
        ILogger<ResourceOwnerPasswordValidator<TUser>> logger)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _logger = logger;
    }

    /// <summary>
    /// Validates the resource owner password credential
    /// </summary>
    /// <param name="context">The context.</param>
    /// <returns></returns>
    public virtual async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        var user = await _userManager.FindByNameAsync(context.UserName);
        if (user != null)
        {
            var result = await _signInManager.CheckPasswordSignInAsync(user, context.Password, true);
            if (result.Succeeded)
            {
                var sub = await _userManager.GetUserIdAsync(user);

                _logger.LogInformation("Credentials validated for username: {username}", context.UserName);

                context.Result = new GrantValidationResult(sub, AuthenticationMethods.Password);
                return;
            }
            else if (result.IsLockedOut)
            {
                _logger.LogInformation("Authentication failed for username: {username}, reason: locked out", context.UserName);
            }
            else if (result.IsNotAllowed)
            {
                _logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", context.UserName);
            }
            else
            {
                _logger.LogInformation("Authentication failed for username: {username}, reason: invalid credentials", context.UserName);
            }
        }
        else
        {
            _logger.LogInformation("No user found matching username: {username}", context.UserName);
        }

        context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
    }
}
